/*
* 有一种树叫Adelson-Velskii-Landi树（AVL树）。
* AVL树是一种自平衡二叉搜索树，意思是任何一个节点的左右两侧子树的高度之差最多为1。
* 既然AVL树是一个BST，我们可以扩展我们写的BST类，只需要覆盖用来维持AVL树平衡的方法，
* 也就是insert、insertNode、removeNode方法。所有其他的BST方法都会被AVL树类继承。
*
* 先理清几个基本概念：
*   1.节点的高度：
*   在AVL树中，需要对每个节点计算右子树高度（hr）和左子树高度（hl）之间的差值，该值（hr-hl）应为0、1或-1，
*   如果结果不是这三个值之一，则需要平衡该AVL树。这就是平衡因子的概念。
*   2.平衡操作
*   在对AVL树添加或移除节点后，要计算并验证树是否需要进行平衡。向AVL树插入节点时，可以执行单旋转或者双旋转两中平衡操作。
*   左左（LL）：向右的单旋转
*       LL使用场景：节点的左侧子节点的高度大于右侧子节点的高度时，并且左侧子节点也是平衡或者左侧较重的情况。
*   右右（RR）：向左的单旋转
*       RR使用场景：同理可推。
*   左右（LR）：向右的双旋转（先LL向右的单旋转，再RR向左的单旋转）
*       LR使用场景：左侧子节点高度大于右侧子节点的高度，并且左侧子节点的右侧较重的情况。
*   右左（RL）：向左的双旋转（先RR向左的单旋转，再LL向右的单旋转）
*       RL使用场景：同理可推。
*
*   左左（LL）：向右的单旋转的具体逻辑：
*       一个"爸爸节点"之下，左侧有一个"左儿子节点"，"左儿子节点"下，左侧有一个"左孙子节点"，右侧也有一个"右孙子节点"，"左孙子节点"下有一个"左曾孙节点"
*       "爸爸节点"之下的右侧，有一个"右儿子节点"，没了。如此一来，左右两系极不平衡，需要被平衡。
*       第一步，多子多孙的"左儿子节点"这一系之下（包括左儿子）所有节点，先拎出来，用一个中间变量保存他们。
*       第二步，"左儿子"这一系被拎出来之后，就剩下"爸爸节点"和"右儿子节点"相依为命，这可不行，需要加人。此时"左儿子"之下的"右孙子"非常能体会孤家孤人的感觉，
*           主动要求来赡养"老人家"，于是"右孙子节点"便脱离原来的"左儿子节点"这一系，跑到"爸爸节点"之下，成为"新左儿子节点"。于是乎，原来的"左儿子节点"下面
*           只剩下"左孙子节点"和"左曾孙节点"。
*       第三步，原来的"左儿子节点"眼睁睁看着自己的"儿子"居然和自己平起平坐都成了儿子，甚至还抢了自己的位置。这还了得，于是一咬牙一跺脚，"左儿子节点"把
*           "爸爸节点"这一系接过来，把 "爸爸节点" 当 "右儿子"养。如此一来，原来的"左儿子节点"成了"新爸爸节点"，而他之前的"左孙子"和"左曾孙"都辈分长一级，
*           原来的"爸爸节点"成了"新右儿子节点"，那个刚刚成为"新左儿子节点"的，此时也变成了"新右儿子节点"的"新左孙子节点"
*       简单来说，就是爸爸变左儿子，左儿子变爸爸，右孙子变左孙子
*
* */
//树中每个存储数据的地方叫元素节点，所以要创建一个节点的类
class Node{
    constructor(key){ //这里的key不像是栈或者队列里面的索引，树的key直接对应的就是节点所存储的数据值
        this.key = key //存储节点值
        this.left = null //索引，指向左侧子节点，就像双向链表，链表是上下结构（指针域指向上和下），树是左右结构（指向左和右）
        this.right = null //索引，指向右侧子节点
    }
}

//二叉搜索树类
class BinarySearchTree{
    constructor(){
        this.root = null //二叉树的根节点，默认值为null，初始化
    }

    insert(key){ //往二叉树里面插入新的值
        if(!this.root){ //如果根节点没有值，那么就插到二叉树的根节点中
            this.root = new Node(key)
        }else{ //如果根节点已经有了值，做判断，比较插入的子节点的值与父节点的值得大小，来决定是左侧子节点还是右侧子节点
            //调用往某个节点插入值的方法
            this.insertNode(this.root,key) //this.root是因为要每次插入新值的时候，要从根节点开始做比较大小的判断并插入值
        }
    }

    insertNode(node,key){ //往某个节点插入一个值，node表示父节点，key表示子节点的值，子节点的值要和父节点的值比较后决定左右侧
        //这么写的原因是为了在已经有了大量数据的时候，能够递归地调用
        if(key < node.key){ //如果待插入的值 比 父节点的值小，插左边
            //还要细分，如果该父节点的左边已经有了一个子节点（因为不可能每次都是插入根节点的子节点，万一有很多层），那么就要判断
            if(node.left){ //该父节点左侧已有子节点，待插入的值 就要成为 该子节点 的 子节点（不确定有多少层，所以要递归）
                this.insertNode(node.left,key) //递归调用往某个节点插入值的方法，这次是左侧子节点的值与待插入值比较大小
            }else{ //如果该父节点左侧没有子节点，直接插入成为该父节点的子节点
                node.left = new Node(key)
            }
        }else{ //大于或是等于插右边同样右侧子节点也要进行递归判断
            if(node.right){ //该父节点右侧已有子节点，待插入的值 就要成为 该子节点 的 子节点（不确定有多少层，所以要递归）
                this.insertNode(node.right,key) //递归调用往某个节点插入值的方法，这次是右侧子节点的值与待插入值比较大小
            }else{ //如果该父节点右侧没有子节点，直接插入成为该父节点的子节点
                node.right = new Node(key)
            }
        }
    }

    min(){ //查询整个二叉树的最小值，同样需要一个递归方法，因为无法得知哪个节点才是最小值
        return this.minNode(this.root) //返回 从根节点开始递归判断查找最小值的函数方法 的 返回值
    }
    //注意区分min方法和minNode方法的区别，一个是查找整个树的最小值，一个是从某个节点开始往下的最小值（不一定是整个树的最小值）
    minNode(node){ //从某个指定的节点开始 递归地判断查找最小值方法，node表示传入的节点
        let current = node //将每一次当前传入的节点保存在变量中
        while(current && current.left){ //当 当前节点存在值 并且 当前节点的左侧子节点也存在值时，说明还有更小的值
            current = current.left //就将左侧子节点的变成当前节点，继续进行循环比较
        }
        //否则直接进行返回，左侧没有子节点就表明当前节点就是最小值所在节点
        return current
    }

    max(){ //查询整个二叉树的最大值，同样需要一个递归方法，因为无法得知哪个节点才是最小值
        return this.maxNode(this.root) //返回 从根节点开始递归判断查找最小值的函数方法 的 返回值
    }
    //注意区分max方法和maxNode方法的区别，一个是查找整个树的最大值，一个是从某个节点开始往下的最大值（不一定是整个树的最大值）
    maxNode(node){ //从某个指定的节点开始 递归地判断查找最大值方法，node表示传入的节点
        let current = node //将每一次当前传入的节点保存在变量中
        while(current && current.right){ //当 当前节点存在值 并且 当前节点的右侧子节点也存在值时，说明还有更大的值
            current = current.right //就将右侧子节点的变成当前节点，继续进行循环比较
        }
        //否则直接进行返回，右侧没有子节点就表明当前节点就是最大值所在节点
        return current
    }

    //中序遍历
    inOrderTraverse(cb){ //接收一个回调函数，中序遍历排序
        this.inOrderTraverseNode(this.root,cb) //从根节点开始中序遍历排序
    }

    inOrderTraverseNode(node,cb){ //中序遍历递归方法
        if(node){ //当该节点存在的时候才做遍历操作
            this.inOrderTraverseNode(node.left,cb)
            //一直往左边子节点寻找，如果下面还有左侧子节点又会进入这一层的递归方法，但是不会运行到下一行cb()，
            //因为当前停留在这一层并进入这一层递归里面了（这就是不会立马执行cb的原因），如果还有左侧子节点，又是重复这个操作
            //直到没有下一个左侧子节点才会跳出当前层的递归，回到上一层，进行下一行cb()操作
            cb(node.key) //直到没有左侧子节点就把现在停留在节点的值放到回调函数进行操作
            this.inOrderTraverseNode(node.right,cb) //同理，依旧要判断这个右侧子节点的下面还有没有左侧子节点，如果有，进入当前层递归
        }
    }

    //先序遍历
    preOrderTraverse(cb){ //接收一个回调函数，先序遍历数据的结构
        this.preOrderTraverseNode(this.root,cb) //从根节点开始先序遍历
    }

    preOrderTraverseNode(node,cb){ //先序遍历递归方法
        if(node){ //当该节点存在的时候才做遍历操作
            cb(node.key) //把遍历到的值放到回调函数进行操作
            this.preOrderTraverseNode(node.left,cb)
            //一直往左边子节点寻找，如果下面还有左侧子节点又会进入这一层的递归方法，但是不会运行到下一行
            //因为当前停留在这一层并进入这一层递归里面了（这就是不会立马执行cb的原因），如果还有左侧子节点，又是重复这个操作
            //直到没有下一个左侧子节点才会跳出当前层的递归，回到上一层，进行下一行操作
            this.preOrderTraverseNode(node.right,cb) //同理，依旧要判断这个右侧子节点的下面还有没有子节点，如果有，进入当前层递归
        }
    }

    //后序遍历
    postOrderTraverse(cb){ //接收一个回调函数，后序遍历
        this.postOrderTraverseNode(this.root,cb) //从根节点开始后序遍历排序
    }

    postOrderTraverseNode(node,cb){ //后序遍历递归方法
        if(node){ //当该节点存在的时候才做遍历操作
            this.postOrderTraverseNode(node.left,cb)
            //一直往左边子节点寻找，如果下面还有左侧子节点又会进入这一层的递归方法，但是不会运行到下一行，
            //因为当前停留在这一层并进入这一层递归里面了（这就是不会立马执行下一行的原因），如果还有左侧子节点，又是重复这个操作
            //直到没有下一个左侧子节点才会跳出当前层的递归，回到上一层，进行下一行操作
            this.postOrderTraverseNode(node.right,cb) //同理，依旧要判断这个右侧子节点的下面还有没有左侧子节点，如果有，进入当前层递归
            cb(node.key) //直到左侧子节点之下没有右侧子节点就把现在停留在节点的值放到回调函数进行操作
        }
    }

    //查找功能
    searchKey(key){
        return this.searchNode(this.root,key)
    }

    searchNode(node,key){ //递归方法
        //先判断树里面有没有东西
        if(node){ //树中必须有节点才能进行搜索
            //再判断值的大小，决定从哪边搜
            if(key < node.key){ //如果传入的值比当前节点的值小，继续搜索左侧子节点
                return this.searchNode(node.left,key)
            }else if(key > node.key){ //如果传入的值比当前节点的值大，继续搜索右侧子节点
                return this.searchNode(node.right,key)
            }else{ //既不是大于也不是小于那就是搜到了
                return true
            }
        }else{ //如果节点都不存在那就不用搜了
            return false
        }
    }

    //删除功能
    removeKey(key){
        this.root = this.removeNode(this.root,key)
    }

    removeNode(node,key){
        if(node){ //树里面有节点存在才能删
            if(key < node.key){ //从左侧开始搜
                node.left = this.removeNode(node.left,key)
                return node //返回拼接后的节点
            }else if(key > node.key){ //从右侧开始搜
                node.right = this.removeNode(node.right,key)
                return node //返回拼接后的节点
            }else{ //找到了要删除的对象
                //第一种情况：待删除的节点下面左右两侧都有子节点
                if(node.left && node.right){
                    console.log("替换前节点",node)
                    console.log("替换前右侧子节点",node.right)

                    let target = this.minNode(node.right) //将待删除节点的右侧子节点的所有子节点中最小的子节点找出来，即最小孙子节点
                    node.key = target.key //最小孙子节点替补到被删除节点的位置上
                    node.right = this.removeNode(node.right,key) //把那个找来做替补的最小孙子节点从原来的孙子位置上删掉

                    console.log("替换后节点",node)
                    console.log("替换后右侧子节点",node.right)

                    return node
                }

                //第二种情况：待删除节点左侧或者右侧有子节点
                if(node.left || node.right){
                    if(node.left){ //如果待删除节点左侧有子节点，左侧子节点替代被删除节点
                        node = node.left
                    }
                    if(node.right){ //同理
                        node = node.right
                    }
                    return node //返回替代后的节点
                }

                //第三种情况：待删除节点就是一个叶节点
                node = null
                return node
            }
        }else{
            return null
        }
    }

    //修改功能
    updateKey(key,key1){
        return this.updateNode(this.root,key,kye1)
    }

    updateNode(node,key,key1){ //递归方法
        //先判断树里面有没有东西
        if(node){ //树中必须有节点才能进行搜索
            //再判断值的大小，决定从哪边搜
            if(key < node.key){ //如果传入的值比当前节点的值小，继续搜索左侧子节点
                return this.updateNode(node.left,key,key1)
            }else if(key > node.key){ //如果传入的值比当前节点的值大，继续搜索右侧子节点
                return this.updateNode(node.right,key,key1)
            }else{ //既不是大于也不是小于那就是搜到了
                node.key = key1
                return true
            }
        }else{ //如果节点都不存在那就不用搜了
            return false
        }
    }
}

const BalanceFactor = { //定义一个平衡因子标准映射表，方便后期阅读
    UNBALANCED_RIGHT:-2, //右边极其不平衡
    SLIGHTLY_UNBALANCED_RIGHT:-1, //右边有点不平衡
    BALANCED:0, //左右都平
    UNBALANCED_LEFT:1, //左边有点不平衡
    SLIGHTLY_UNBALANCED_LEFT:2, //左边极其不平衡
}

class AVLTree extends BinarySearchTree{
    constructor(){
        super()
    }

    getNodeHeight(node){ //获取传入节点高度
        if(node){ //判断节点是否存在
            return Math.max(this.getNodeHeight(node.left),this.getNodeHeight(node.right)) + 1 //发生了隐式类型转换，递归入口
            //如果是根节点传入，左侧节点没有，返回-1，右侧节点也没有，返回-1，Math.max(-1,-1)取得就是-1，-1+1等于0
        }else{
            return -1 //递归出口
        }
    }

    getBalanceFactor(node){ //计算平衡因子
        const heightDifference = this.getNodeHeight(node.left) - this.getNodeHeight(node.right) //用左边子树高度减去右边子树高度
        switch(heightDifference){
            case -2:
                return BalanceFactor.UNBALANCED_RIGHT
            case -1:
                return BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT
            case 0:
                return BalanceFactor.BALANCED
            case 1:
                return BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
            case 2:
                return BalanceFactor.UNBALANCED_LEFT
        }
    }

    //整个左侧子树较重，将左侧子树右旋
    rotationLL(node){ //左左（LL）：向右的单旋转
        let temp = node.left //左儿子被保存并暂时出走
        node.left = temp.right //左儿右孙子成为爸爸的新左儿子
        temp.right = node //爸爸成为老左儿子的新右儿子
        return temp
    }

    //整个右侧子树较重，将右侧子树左旋
    rotationRR(node){ //右右（RR）：向左的单旋转
        let temp = node.right //右儿子被保存并暂时出走
        node.right = temp.left //右儿左孙子成为爸爸的新右儿子
        temp.left = node //爸爸成为老右儿子的新左儿子
        return temp
    }

    //左侧子树的右侧较重，先变成整个左侧子树较重，再将整个左侧子树右旋
    rotationLR(node){ //左右（LR）：向右的双旋转（先RR向左的单旋转，再LL向右的单旋转）
        node.left = this.rotationRR(node.left) //先把左侧子节点左单旋转，左侧子树传入RR就会变成先平衡左侧子树的右侧较重部分，使其变成整个左侧子树较重
        return this.rotationLL(node) //当整个左侧子树较重时，就进行向右的单旋转
    }

    //右侧子树的左侧较重，先变成整个右侧子树较重，再将整个右侧子树左旋
    rotationRL(node){ //右左（RL）：向左的双旋转（先LL向右的单旋转，再RR向左的单旋转）
        node.right = this.rotationLL(node.right) //先把右侧子节点右单旋转，右侧子树传入LL就会变成先平衡右侧子树的左侧较重部分，使其变成整个右侧子树较重
        return this.rotationRR(node) //当整个右侧子树较重时，就进行向左的单旋转
    }

    insert(key) {
        this.root = this.insertNode(this.root,key)
    }

    insertNode(node, key) {
        if(!node){ //如果node节点不存在，生成并返回node节点
            return new Node(key)
        }else if(key < node.key){ //如果待插入的值小于当前节点的值，插入节点的左侧子节点
            node.left = this.insertNode(node.left,key)
        }else if(key > node.key){ //如果待插入的值大于当前节点的值，插入节点的右侧子节点
            node.right = this.insertNode(node.right,key)
        }else{ //如果值重复了，返回节点
            return node
        }

        //上面的代码和BST一模一样，现在下面重点来了
        //插入完成之后，判断当前节点是否平衡，如不平衡，则进行平衡操作
        switch(this.getBalanceFactor(node)){ //每次都平衡一点点，立马对新插入的节点进行平衡，而不是等全部插完再一次性平衡
            //只有极不平衡才需要被平衡，即高度差大于1时，左边极不平衡，右边极不平衡
            case BalanceFactor.UNBALANCED_LEFT:
                if(key < node.left.key){ //插入的新值小于左侧子节点的值，需要进行LL平衡
                    node = this.rotationLL(node)
                }else{ //插入的新值大于父节点的值
                    node = this.rotationLR(node)
                }
                break
            case BalanceFactor.UNBALANCED_RIGHT:
                if(key < node.right.key){ //插入的新值小于右侧子节点的值
                    node = this.rotationRL(node)
                }else{
                    node = this.rotationRR(node)
                }
                break
        }

        return node //将平衡好的node返回
    }

    removeNode(node, key) {
        if(!node){ //如果要删除的节点压根不存在
            return null
        }
        node = super.removeNode(node,key)
        switch(this.getBalanceFactor(node)){ //每次都平衡一点点，立马对新删除后剩下的的节点进行平衡，而不是等全部删完再一次性平衡
            //只有极不平衡才需要被平衡，即高度差大于1时，左边极不平衡，右边极不平衡
            case BalanceFactor.UNBALANCED_LEFT:
                if(
                    this.getBalanceFactor(node.left) === BalanceFactor.BALANCED ||
                    this.getBalanceFactor(node.left) === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
                ){
                    node = this.rotationLL(node)
                }else{ //插入的新值大于父节点的值
                    node = this.rotationLR(node)
                }
                break
            case BalanceFactor.UNBALANCED_RIGHT:
                if(
                    this.getBalanceFactor(node.right) === BalanceFactor.BALANCED ||
                    this.getBalanceFactor(node.right) === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
                ){
                    node = this.rotationRR(node)
                }else{
                    node = this.rotationRL(node)
                }
                break
        }

        return node //将平衡好的node返回
    }
}

let treeData = new AVLTree()
treeData.insert(10)
treeData.insert(5)
treeData.insert(3)
treeData.insert(15)
treeData.insert(13)